/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.spartacusrex.spartacuside;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Handler;
import android.os.Environment;
import android.text.ClipboardManager;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;

import com.spartacusrex.spartacuside.model.TextRenderer;
import com.spartacusrex.spartacuside.model.UpdateCallback;
import com.spartacusrex.spartacuside.session.TerminalEmulator;
import com.spartacusrex.spartacuside.session.TermSession;
import com.spartacusrex.spartacuside.session.TranscriptScreen;
import com.spartacusrex.spartacuside.util.TermSettings;
import com.spartacusrex.spartacuside.util.hardkeymappings;
import com.spartacusrex.spartacuside.util.keydata;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.Vector;

import org.dyne.zshaolin.R;
/**
 * A view on a transcript and a terminal emulator. Displays the text of the
 * transcript and the current cursor position of the terminal emulator.
 */
public class EmulatorView extends View implements GestureDetector.OnGestureListener {

    private final String TAG = "EmulatorView";
    private final boolean LOG_KEY_EVENTS = false;

    private TermSettings mSettings;
    private TermViewFlipper mViewFlipper;

    /**
     * We defer some initialization until we have been layed out in the view
     * hierarchy. The boolean tracks when we know what our size is.
     */
    private boolean mKnownSize;

    private int mVisibleWidth;
    private int mVisibleHeight;
    private Rect mVisibleRect = new Rect();

    private TermSession mTermSession;

    /**
     * Our transcript. Contains the screen and the transcript.
     */
    private TranscriptScreen mTranscriptScreen;

    /**
     * Total width of each character, in pixels
     */
    private float mCharacterWidth;

    /**
     * Total height of each character, in pixels
     */
    private int mCharacterHeight;

    /**
     * Used to render text
     */
    private TextRenderer mTextRenderer;

    /**
     * Text size. Zero means 4 x 8 font.
     */
    private int mTextSize;

    private int mCursorStyle;
    private int mCursorBlink;

    /**
     * Foreground color.
     */
    private int mForeground;

    /**
     * Background color.
     */
    private int mBackground;

    /**
     * Used to paint the cursor
     */
    private Paint mCursorPaint;

    private Paint mBackgroundPaint;

    private boolean mUseCookedIme;

    /**
     * Our terminal emulator. We use this to get the current cursor position.
     */
    private TerminalEmulator mEmulator;

    /**
     * The number of rows of text to display.
     */
    private int mRows;

    /**
     * The number of columns of text to display.
     */
    private int mColumns;

    /**
     * The number of columns that are visible on the display.
     */

    private int mVisibleColumns;

    /**
     * The top row of text to display. Ranges from -activeTranscriptRows to 0
     */
    private int mTopRow;

    private int mLeftColumn;

    /**
     * Used to receive data from the remote process.
     */
    private FileOutputStream mTermOut;

    private static final int SCREEN_CHECK_PERIOD = 1000;
    private static final int CURSOR_BLINK_PERIOD = 1000;

    private boolean mCursorVisible = true;

    private boolean mIsSelectingText = false;


    private float mDensity;

    private float mScaledDensity;
    private static final int SELECT_TEXT_OFFSET_Y = -40;
    private int mSelXAnchor = -1;
    private int mSelYAnchor = -1;
    private int mSelX1 = -1;
    private int mSelY1 = -1;
    private int mSelX2 = -1;
    private int mSelY2 = -1;

    /**
     * Used to poll if the view has changed size. Wish there was a better way to do this.
     */
    private Runnable mCheckSize = new Runnable() {

        public void run() {
            updateSize(false);
            mHandler.postDelayed(this, SCREEN_CHECK_PERIOD);
        }
    };

    private Runnable mBlinkCursor = new Runnable() {
        public void run() {
            if (mCursorBlink != 0) {
                mCursorVisible = ! mCursorVisible;
                mHandler.postDelayed(this, CURSOR_BLINK_PERIOD);
            } else {
                mCursorVisible = true;
            }
            // Perhaps just invalidate the character with the cursor.
            invalidate();
        }
    };

    private GestureDetector mGestureDetector;
    private float mScrollRemainder;
    private TermKeyListener mKeyListener;

    private String mImeBuffer = "";

    /**
     * Our message handler class. Implements a periodic callback.
     */
    private final Handler mHandler = new Handler();

    Animation mAnimLeftIn;
    Animation mAnimRightIn;
    Animation mAnimLeftOut;
    Animation mAnimRightOut;

    /**
     * Called by the TermSession when the contents of the view need updating
     */
    private UpdateCallback mUpdateNotify = new UpdateCallback() {
        public void onUpdate() {
            if ( mIsSelectingText ) {
                int rowShift = mEmulator.getScrollCounter();
                mSelY1 -= rowShift;
                mSelY2 -= rowShift;
                mSelYAnchor -= rowShift;
            }
            mEmulator.clearScrollCounter();
            ensureCursorVisible();
            invalidate();
        }
    };

    public UpdateCallback getUpdateCallback() {
        return mUpdateNotify;
    }

    public EmulatorView(Context context, TermSession session, TermViewFlipper viewFlipper, DisplayMetrics metrics) {
        super(context);
        commonConstructor(session, viewFlipper);
        setDensity(metrics);

        mAnimLeftIn   = AnimationUtils.loadAnimation(context, android.R.anim.slide_in_left);
        mAnimLeftOut  = AnimationUtils.loadAnimation(context, R.anim.slide_out_left);
        mAnimRightIn  = AnimationUtils.loadAnimation(context, R.anim.slide_in_right);
        mAnimRightOut = AnimationUtils.loadAnimation(context, android.R.anim.slide_out_right);
    }

    public void setDensity(DisplayMetrics metrics) {
        mDensity = metrics.density;
        mScaledDensity = metrics.scaledDensity;
    }

    public void onResume() {
        updateSize(false);
        mHandler.postDelayed(mCheckSize, SCREEN_CHECK_PERIOD);
        if (mCursorBlink != 0) {
            mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD);
        }
    }

    public void onPause() {
        mHandler.removeCallbacks(mCheckSize);
        if (mCursorBlink != 0) {
            mHandler.removeCallbacks(mBlinkCursor);
        }
    }

    public void updatePrefs(TermSettings settings) {
        mSettings = settings;
        setTextSize((int) (mSettings.getFontSize() * mDensity));
        setCursorStyle(mSettings.getCursorStyle(), mSettings.getCursorBlink());
        setUseCookedIME(mSettings.useCookedIME());
        setColors();
    }

    public void setColors() {
        int[] scheme = mSettings.getColorScheme();
        mForeground = scheme[0];
        mBackground = scheme[1];
        updateText();
    }

    public void resetTerminal() {
        mEmulator.reset();
        invalidate();
    }

    @Override
    public boolean onCheckIsTextEditor() {
        return true;
    }

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        outAttrs.inputType = mUseCookedIme ?
                EditorInfo.TYPE_CLASS_TEXT :
                EditorInfo.TYPE_NULL;
        return new InputConnection() {
            private boolean mInBatchEdit;
            /**
             * Used to handle composing text requests
             */
            private int mCursor;
            private int mComposingTextStart;
            private int mComposingTextEnd;
            private int mSelectedTextStart = 0;
            private int mSelectedTextEnd   = 0;

            private void sendChar(int c) {
                try {
                    mapAndSend(c);
                } catch (IOException ex) {

                }
            }

            private void sendText(CharSequence text) {
                int n = text.length();
                try {
                    for(int i = 0; i < n; i++) {
                        char c = text.charAt(i);
                        mapAndSend(c);
                    }
                    mTermOut.flush();
                } catch (IOException e) {
                    Log.e(TAG, "error writing ", e);
                }
            }

            private void mapAndSend(int c) throws IOException {
                int result = mKeyListener.mapControlChar(c);
                if (result < TermKeyListener.KEYCODE_OFFSET) {
//                    Log.v("SpartacusRex","EMVIEW : 1) mapAndSend "+c+" "+result);
                    
                    //Check for ALT
                    //if(mAl)

                    mTermOut.write(result);
                } else {
                    int code = result - TermKeyListener.KEYCODE_OFFSET;
//                    Log.v("SpartacusRex","EMVIEW : 2) mapAndSend "+c+" "+code);
                    mKeyListener.handleKeyCode(result - TermKeyListener.KEYCODE_OFFSET, mTermOut, getKeypadApplicationMode());
                }
            }

            public boolean beginBatchEdit() {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "beginBatchEdit");
                }
                setImeBuffer("");
                mCursor = 0;
                mComposingTextStart = 0;
                mComposingTextEnd = 0;
                mInBatchEdit = true;
                return true;
            }

            public boolean clearMetaKeyStates(int arg0) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "clearMetaKeyStates " + arg0);
                }
                return false;
            }

            public boolean commitCompletion(CompletionInfo arg0) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "commitCompletion " + arg0);
                }
                return false;
            }

            public boolean endBatchEdit() {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "endBatchEdit");
                }
                mInBatchEdit = false;
                return true;
            }

            public boolean finishComposingText() {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "finishComposingText");
                }
                sendText(mImeBuffer);
                setImeBuffer("");
                mComposingTextStart = 0;
                mComposingTextEnd = 0;
                mCursor = 0;
                return true;
            }

            public int getCursorCapsMode(int arg0) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "getCursorCapsMode(" + arg0 + ")");
                }
                return 0;
            }

            public ExtractedText getExtractedText(ExtractedTextRequest arg0,
                    int arg1) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "getExtractedText" + arg0 + "," + arg1);
                }
                return null;
            }

            public CharSequence getTextAfterCursor(int n, int flags) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "getTextAfterCursor(" + n + "," + flags + ")");
                }
                int len = Math.min(n, mImeBuffer.length() - mCursor);
                if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) {
                    return "";
                }
                return mImeBuffer.substring(mCursor, mCursor + len);
            }

            public CharSequence getTextBeforeCursor(int n, int flags) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "getTextBeforeCursor(" + n + "," + flags + ")");
                }
                int len = Math.min(n, mCursor);
                if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) {
                    return "";
                }
                return mImeBuffer.substring(mCursor-len, mCursor);
            }

            public boolean performContextMenuAction(int arg0) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "performContextMenuAction" + arg0);
                }
                return true;
            }

            public boolean performPrivateCommand(String arg0, Bundle arg1) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "performPrivateCommand" + arg0 + "," + arg1);
                }
                return true;
            }

            public boolean reportFullscreenMode(boolean arg0) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "reportFullscreenMode" + arg0);
                }
                return true;
            }

            public boolean commitCorrection (CorrectionInfo correctionInfo) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "commitCorrection");
                }
                return true;
            }

            public boolean commitText(CharSequence text, int newCursorPosition) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "commitText(\"" + text + "\", " + newCursorPosition + ")");
                }
                clearComposingText();
                sendText(text);
                setImeBuffer("");
                mCursor = 0;
                return true;
            }

            private void clearComposingText() {
                setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) +
                    mImeBuffer.substring(mComposingTextEnd));
                if (mCursor < mComposingTextStart) {
                    // do nothing
                } else if (mCursor < mComposingTextEnd) {
                    mCursor = mComposingTextStart;
                } else {
                    mCursor -= mComposingTextEnd - mComposingTextStart;
                }
                mComposingTextEnd = mComposingTextStart = 0;
            }

            public boolean deleteSurroundingText(int leftLength, int rightLength) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "deleteSurroundingText(" + leftLength +
                            "," + rightLength + ")");
                }
                if (leftLength > 0) {
                    for (int i = 0; i < leftLength; i++) {
                        sendKeyEvent(
                            new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
                    }
                } else if ((leftLength == 0) && (rightLength == 0)) {
                    // Delete key held down / repeating
                    sendKeyEvent(
                        new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
                }
                // TODO: handle forward deletes.
                return true;
            }

            public boolean performEditorAction(int actionCode) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "performEditorAction(" + actionCode + ")");
                }
                if (actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) {
                    // The "return" key has been pressed on the IME.
                    sendText("\n");
                }
                return true;
            }

            public boolean sendKeyEvent(KeyEvent event) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "sendKeyEvent(" + event + ")");
                }
                // Some keys are sent here rather than to commitText.
                // In particular, del and the digit keys are sent here.
                // (And I have reports that the HTC Magic also sends Return here.)
                // As a bit of defensive programming, handle every key.
                dispatchKeyEvent(event);
                return true;
            }

            public boolean setComposingText(CharSequence text, int newCursorPosition) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "setComposingText(\"" + text + "\", " + newCursorPosition + ")");
                }
                setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) +
                    text + mImeBuffer.substring(mComposingTextEnd));
                mComposingTextEnd = mComposingTextStart + text.length();
                mCursor = newCursorPosition > 0 ? mComposingTextEnd + newCursorPosition - 1
                        : mComposingTextStart - newCursorPosition;
                return true;
            }

            public boolean setSelection(int start, int end) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "setSelection" + start + "," + end);
                }
                int length = mImeBuffer.length();
                if (start == end && start > 0 && start < length) {
                    mSelectedTextStart = mSelectedTextEnd = 0;
                    mCursor = start;
                } else if (start < end && start > 0 && end < length) {
                    mSelectedTextStart = start;
                    mSelectedTextEnd = end;
                    mCursor = start;
                }
                return true;
            }

            public boolean setComposingRegion(int start, int end) {
                if (TermDebug.LOG_IME) {
                    Log.w(TAG, "setComposingRegion " + start + "," + end);
                }
                if (start < end && start > 0 && end < mImeBuffer.length()) {
                    clearComposingText();
                    mComposingTextStart = start;
                    mComposingTextEnd = end;
                }
                return true;
            }

            public CharSequence getSelectedText(int flags) {

                try {

                    if (TermDebug.LOG_IME) {
                        Log.w(TAG, "getSelectedText " + flags);
                    }

                    if (mImeBuffer.length() < 1) {
                        return "";
                    }

                    return mImeBuffer.substring(mSelectedTextStart, mSelectedTextEnd + 1);

                } catch (Exception e) {
                    
                }

                return "";
            }

        };
    }

    private void setImeBuffer(String buffer) {
        if (!buffer.equals(mImeBuffer)) {
            invalidate();
        }
        mImeBuffer = buffer;
    }

    public boolean getKeypadApplicationMode() {
        return mEmulator.getKeypadApplicationMode();
    }

    private void commonConstructor(TermSession session, TermViewFlipper viewFlipper) {
        mTextRenderer = null;
        mCursorPaint = new Paint();
        mCursorPaint.setARGB(255,128,128,128);
        mBackgroundPaint = new Paint();
        mTopRow = 0;
        mLeftColumn = 0;
        mGestureDetector = new GestureDetector(this);
        // mGestureDetector.setIsLongpressEnabled(false);
/*
        mGestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
            public boolean onSingleTapConfirmed(MotionEvent arg0) {
               return true;
            }

            public boolean onDoubleTap(MotionEvent arg0) {
                //Toggle the Soft Keyboard..
                InputMethodManager imm = (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,0);
                return true;
            }

            public boolean onDoubleTapEvent(MotionEvent arg0) {
                return true;
            }
        });
*/
        setVerticalScrollBarEnabled(true);
        setFocusable(true);
        setFocusableInTouchMode(true);

        initialize(session, viewFlipper);
    }

    @Override
    protected int computeVerticalScrollRange() {
        return mTranscriptScreen.getActiveRows();
    }

    @Override
    protected int computeVerticalScrollExtent() {
        return mRows;
    }

    @Override
    protected int computeVerticalScrollOffset() {
        return mTranscriptScreen.getActiveRows() + mTopRow - mRows;
    }

    /**
     * Call this to initialize the view.
     *
     * @param session The terminal session this view will be displaying
     */
    private void initialize(TermSession session, TermViewFlipper viewFlipper) {
        mTermSession = session;
        mTranscriptScreen = session.getTranscriptScreen();
        mEmulator = session.getEmulator();
        mTermOut = session.getTermOut();

        mViewFlipper = viewFlipper;

        mKeyListener = new TermKeyListener();
        mTextSize = 10;
        mForeground = TermSettings.WHITE;
        mBackground = TermSettings.BLACK;
        updateText();

        requestFocus();
    }

    public TermSession getTermSession() {
        return mTermSession;
    }

    /**
     * Page the terminal view (scroll it up or down by delta screenfulls.)
     *
     * @param delta the number of screens to scroll. Positive means scroll down,
     *        negative means scroll up.
     */
    public void page(int delta) {
        mTopRow =
                Math.min(0, Math.max(-(mTranscriptScreen
                        .getActiveTranscriptRows()), mTopRow + mRows * delta));
        invalidate();
    }

    /**
     * Page the terminal view horizontally.
     *
     * @param deltaColumns the number of columns to scroll. Positive scrolls to
     *        the right.
     */
    public void pageHorizontal(int deltaColumns) {
        mLeftColumn =
                Math.max(0, Math.min(mLeftColumn + deltaColumns, mColumns
                        - mVisibleColumns));
        invalidate();
    }

    /**
     * Sets the text size, which in turn sets the number of rows and columns
     *
     * @param fontSize the new font size, in pixels.
     */
    public void setTextSize(int fontSize) {
        mTextSize = fontSize;
        updateText();
    }

    public void setCursorStyle(int style, int blink) {
        mCursorStyle = style;
        if (blink != 0 && mCursorBlink == 0) {
            mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD);
        } else if (blink == 0 && mCursorBlink != 0) {
            mHandler.removeCallbacks(mBlinkCursor);
        }
        mCursorBlink = blink;
    }

    public void setUseCookedIME(boolean useCookedIME) {
        mUseCookedIme = useCookedIME;
    }

    // Begin GestureDetector.OnGestureListener methods

    public boolean onSingleTapUp(MotionEvent e) {
        return true;
    }

    public void onLongPress(MotionEvent e) {
        showContextMenu();
    }

    public boolean onScroll(MotionEvent e1, MotionEvent e2,
            float distanceX, float distanceY) {
        distanceY += mScrollRemainder;
        int deltaRows = (int) (distanceY / mCharacterHeight);
        mScrollRemainder = distanceY - deltaRows * mCharacterHeight;
        mTopRow =
            Math.min(0, Math.max(-(mTranscriptScreen
                    .getActiveTranscriptRows()), mTopRow + deltaRows));
        invalidate();

        return true;
   }

    public void onSingleTapConfirmed(MotionEvent e) {
    }

    public boolean onJumpTapDown(MotionEvent e1, MotionEvent e2) {
       // Scroll to bottom
       mTopRow = 0;
       invalidate();
       return true;
    }

    public boolean onJumpTapUp(MotionEvent e1, MotionEvent e2) {
        // Scroll to top
        mTopRow = -mTranscriptScreen.getActiveTranscriptRows();
        invalidate();
        return true;
    }


    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
            float velocityY) {
	return true;
/*
        if (Math.abs(velocityX) > Math.abs(velocityY)) {
            // Assume user wanted side to side movement
            if (velocityX > 0) {
                mViewFlipper.setOutAnimation(mAnimRightOut);
                mViewFlipper.setInAnimation(mAnimLeftIn);

                // Left to right swipe -- previous window
                mViewFlipper.showPrevious();
            } else {
                mViewFlipper.setOutAnimation(mAnimLeftOut);
                mViewFlipper.setInAnimation(mAnimRightIn);

                // Right to left swipe -- next window
                mViewFlipper.showNext();
            }
        } else {
            // TODO: add animation man's (non animated) fling
            mScrollRemainder = 0.0f;
            onScroll(e1, e2, 2 * velocityX, -2 * velocityY);
        }
        return true;
*/
    }

    public void onShowPress(MotionEvent e) {
    }

    public boolean onDown(MotionEvent e) {
        mScrollRemainder = 0.0f;
        return true;
    }

    // End GestureDetector.OnGestureListener methods

    @Override public boolean onTouchEvent(MotionEvent ev) {
        if (mIsSelectingText) {
            return onTouchEventWhileSelectingText(ev);
        } else {
            return mGestureDetector.onTouchEvent(ev);
        }
    }

    private boolean onTouchEventWhileSelectingText(MotionEvent ev) {
        int action = ev.getAction();
        int cx = (int)(ev.getX() / mCharacterWidth);
        int cy = Math.max(0,
                (int)((ev.getY() + SELECT_TEXT_OFFSET_Y * mScaledDensity)
                        / mCharacterHeight) + mTopRow);
        switch (action) {
        case MotionEvent.ACTION_DOWN:
            mSelXAnchor = cx;
            mSelYAnchor = cy;
            mSelX1 = cx;
            mSelY1 = cy;
            mSelX2 = mSelX1;
            mSelY2 = mSelY1;
            break;
        case MotionEvent.ACTION_MOVE:
        case MotionEvent.ACTION_UP:
            int minx = Math.min(mSelXAnchor, cx);
            int maxx = Math.max(mSelXAnchor, cx);
            int miny = Math.min(mSelYAnchor, cy);
            int maxy = Math.max(mSelYAnchor, cy);
            mSelX1 = minx;
            mSelY1 = miny;
            mSelX2 = maxx;
            mSelY2 = maxy;
            if (action == MotionEvent.ACTION_UP) {
                ClipboardManager clip = (ClipboardManager)
                     getContext().getApplicationContext()
                         .getSystemService(Context.CLIPBOARD_SERVICE);
                clip.setText(getSelectedText().trim());
                toggleSelectingText();
            }
            invalidate();
            break;
        default:
            toggleSelectingText();
            invalidate();
            break;
        }
        return true;
    }

    @Override
    public boolean onKeyDown(int zKeyCode, KeyEvent event) {
        if (LOG_KEY_EVENTS) {
            Log.v("Terminal IDE", "EmulatorView onKeyDown TOP EVENT code:"+zKeyCode);
        }

        //Could be scanning..
        TermService.keyLoggerKey(zKeyCode);

        //Check with HardKey Mappings..!
        KeyEvent newevent = handleKeyCodeMapper(event.getAction(), zKeyCode);
        if(newevent == null){
            //Function press..
            return true;
        }

        //The new Key Code
        int keyCode = newevent.getKeyCode();

        if (handleControlKey(keyCode, true)) {
            return true;
        } else if (handleFnKey(keyCode, true)) {
            //Send the escape key
            try {
                mKeyListener.keyDown(TermKeyListener.KEYCODE_ESCAPE, newevent, mTermOut, getKeypadApplicationMode());
            } catch (IOException iOException) {
            }
            return true;
        } else if (isSystemKey(keyCode, newevent) && keyCode!=122 && keyCode!=123 && keyCode!=92 && keyCode!=93) {
            // Don't intercept the system keys And the HOME /END / PGUP / PGDOWN KEYS
            return super.onKeyDown(keyCode, newevent);
        }

        try {
            mKeyListener.keyDown(keyCode, newevent, mTermOut,getKeypadApplicationMode());
        } catch (IOException e) {
            // Ignore I/O exceptions
        }

        return true;
    }

    @Override
    public boolean onKeyUp(int zKeyCode, KeyEvent event) {
        if (LOG_KEY_EVENTS) {
            Log.w(TAG, "onKeyUp " + zKeyCode);
        }

        //Check with HardKey Mappings..!
        KeyEvent newevent = handleKeyCodeMapper(event.getAction(), zKeyCode);
        if(newevent == null){
            //Function press..
            return true;
        }

        //The new Key Code
        int keyCode = newevent.getKeyCode();

        if (handleControlKey(keyCode, false)) {
            return true;
        } else if (handleFnKey(keyCode, false)) {
            mKeyListener.keyUp(TermKeyListener.KEYCODE_ESCAPE);   
            return true;
        } else if (isSystemKey(keyCode, newevent)) {
            // Don't intercept the system keys
            return super.onKeyUp(keyCode, newevent);
        }

        mKeyListener.keyUp(keyCode);
        return true;
    }

    private KeyEvent handleKeyCodeMapper(int zAction, int zKeyCode){
        //Check with HardKey Mappings..!
        KeyEvent newevent = new KeyEvent(zAction, zKeyCode);

        if(TermService.isHardKeyEnabled()){
            int hardmap = TermService.isSpecialKeyCode(zKeyCode);

            //Valid.. ?
            if(hardmap != -1){
                //Its a special key code..
                if(hardmap == hardkeymappings.HARDKEY_CTRL_LEFT || hardmap == hardkeymappings.HARDKEY_CTRL_RIGHT){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_CTRL_LEFT);

                }else if(hardmap == hardkeymappings.HARDKEY_ALT_LEFT || hardmap == hardkeymappings.HARDKEY_ALT_RIGHT){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_ALT_LEFT);

                }else if(hardmap == hardkeymappings.HARDKEY_ESCAPE){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_ESCAPE);

                }else if(hardmap == hardkeymappings.HARDKEY_FUNCTION){
                    //Just Update the Function Key Settings
                    mKeyListener.handleFunctionKey(false);
                    return null;
                
                }else if(hardmap == hardkeymappings.HARDKEY_TAB){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_TAB);

                }else if(hardmap == hardkeymappings.HARDKEY_LSHIFT || hardmap == hardkeymappings.HARDKEY_RSHIFT){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_SHIFT_LEFT);

                }else if(hardmap == hardkeymappings.HARDKEY_SPACE){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_SPACE);

                }else if(hardmap == hardkeymappings.HARDKEY_ENTER){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_ENTER);

                }else if(hardmap == hardkeymappings.HARDKEY_DELETE){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_FORWARD_DEL);

                }else if(hardmap == hardkeymappings.HARDKEY_BACKSPACE){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_DEL);

                }else if(hardmap == hardkeymappings.HARDKEY_UP){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_DPAD_UP);

                }else if(hardmap == hardkeymappings.HARDKEY_DOWN){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_DPAD_DOWN);

                }else if(hardmap == hardkeymappings.HARDKEY_LEFT){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_DPAD_LEFT);

                }else if(hardmap == hardkeymappings.HARDKEY_RIGHT){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_DPAD_RIGHT);

                }else if(hardmap == hardkeymappings.HARDKEY_PGUP){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_PAGE_UP);

                }else if(hardmap == hardkeymappings.HARDKEY_PGDOWN){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_PAGE_DOWN);

                }else if(hardmap == hardkeymappings.HARDKEY_HOME){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_MOVE_HOME);

                }else if(hardmap == hardkeymappings.HARDKEY_END){
                    newevent = new KeyEvent(zAction, TermKeyListener.KEYCODE_MOVE_END);
                }
            }
        }

        return newevent;
    }


    private boolean handleControlKey(int keyCode, boolean down) {
        if (keyCode == mSettings.getControlKeyCode()) {
            if (LOG_KEY_EVENTS) {
                Log.w(TAG, "handleControlKey " + keyCode);
            }
            mKeyListener.handleControlKey(down);
            return true;
        }

        return false;
    }

    private boolean handleFnKey(int keyCode, boolean down) {
        if (keyCode == mSettings.getFnKeyCode()) {
            if (LOG_KEY_EVENTS) {
                Log.w(TAG, "handleFnKey " + keyCode);
            }
            //if(down){
                //Send the escape sequence..   
            //}
            //mKeyListener.handleFnKey(down);
            return true;
        }
        return false;
    }

    private boolean handleFunctionKey(boolean down) {
        if (LOG_KEY_EVENTS) {
            Log.w(TAG, "handleFunctionKey ");
        }
        mKeyListener.handleFunctionKey(down);
        return true;
    }

    private boolean isSystemKey(int keyCode, KeyEvent event) {
        return event.isSystem();
    }

    private void updateText() {
        if (mTextSize > 0) {
            mTextRenderer = new PaintRenderer(mTextSize, mForeground,
                    mBackground);
        }
        else {
            mTextRenderer = new Bitmap4x8FontRenderer(getResources(),
                    mForeground, mBackground);
        }
        mBackgroundPaint.setColor(mBackground);
        mCharacterWidth = mTextRenderer.getCharacterWidth();
        mCharacterHeight = mTextRenderer.getCharacterHeight();

        updateSize(true);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        boolean oldKnownSize = mKnownSize;
        if (!mKnownSize) {
            mKnownSize = true;
        }
        updateSize(false);
    }

    private void updateSize(int w, int h) {
        mColumns = Math.max(1, (int) (((float) w) / mCharacterWidth));
        mRows = Math.max(1, h / mCharacterHeight);
        mVisibleColumns = (int) (((float) mVisibleWidth) / mCharacterWidth);

        mTermSession.updateSize(mColumns, mRows);

        // Reset our paging:
        mTopRow = 0;
        mLeftColumn = 0;

        invalidate();
    }

    public void updateSize(boolean force) {
        if (mKnownSize) {
            getWindowVisibleDisplayFrame(mVisibleRect);
            int w = mVisibleRect.width();
            int h = mVisibleRect.height();
            // Log.w("Term", "(" + w + ", " + h + ")");
            if (force || w != mVisibleWidth || h != mVisibleHeight) {
                mVisibleWidth = w;
                mVisibleHeight = h;
                updateSize(mVisibleWidth, mVisibleHeight);
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        updateSize(false);
        int w = getWidth();
        int h = getHeight();
        canvas.drawRect(0, 0, w, h, mBackgroundPaint);
        float x = -mLeftColumn * mCharacterWidth;
        float y = mCharacterHeight;
        int endLine = mTopRow + mRows;
        int cx = mEmulator.getCursorCol();
        int cy = mEmulator.getCursorRow();
        for (int i = mTopRow; i < endLine; i++) {
            int cursorX = -1;
            if (i == cy && mCursorVisible) {
                cursorX = cx;
            }
            int selx1 = -1;
            int selx2 = -1;
            if ( i >= mSelY1 && i <= mSelY2 ) {
                if ( i == mSelY1 ) {
                    selx1 = mSelX1;
                }
                if ( i == mSelY2 ) {
                    selx2 = mSelX2;
                } else {
                    selx2 = mColumns;
                }
            }
            mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2, mImeBuffer);
            y += mCharacterHeight;
        }
    }

    private void ensureCursorVisible() {
        mTopRow = 0;
        if (mVisibleColumns > 0) {
            int cx = mEmulator.getCursorCol();
            int visibleCursorX = mEmulator.getCursorCol() - mLeftColumn;
            if (visibleCursorX < 0) {
                mLeftColumn = cx;
            } else if (visibleCursorX >= mVisibleColumns) {
                mLeftColumn = (cx - mVisibleColumns) + 1;
            }
        }
    }

    public void toggleSelectingText() {
        mIsSelectingText = ! mIsSelectingText;
        setVerticalScrollBarEnabled( ! mIsSelectingText );
        if ( ! mIsSelectingText ) {
            mSelX1 = -1;
            mSelY1 = -1;
            mSelX2 = -1;
            mSelY2 = -1;
        }
    }

    public boolean getSelectingText() {
        return mIsSelectingText;
    }

    public String getSelectedText() {
        return mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2);
    }
}

abstract class BaseTextRenderer implements TextRenderer {
    protected int[] mForePaint = {
            0xff000000, // Black
            0xffff0000, // Red
            0xff00ff00, // green
            0xffffff00, // yellow
            0xff0000ff, // blue
            0xffff00ff, // magenta
            0xff00ffff, // cyan
            0xffffffff  // white -- is overridden by constructor
    };
    protected int[] mBackPaint = {
            0xff000000, // Black -- is overridden by constructor
            0xffcc0000, // Red
            0xff00cc00, // green
            0xffcccc00, // yellow
            0xff0000cc, // blue
            0xffff00cc, // magenta
            0xff00cccc, // cyan
            0xffffffff  // white
    };
    protected final static int mCursorPaint = 0xff808080;

    public BaseTextRenderer(int forePaintColor, int backPaintColor) {
        mForePaint[7] = forePaintColor;
        mBackPaint[0] = backPaintColor;

    }
}

class Bitmap4x8FontRenderer extends BaseTextRenderer {
    private final static int kCharacterWidth = 4;
    private final static int kCharacterHeight = 8;
    private Bitmap mFont;
    private int mCurrentForeColor;
    private int mCurrentBackColor;
    private float[] mColorMatrix;
    private Paint mPaint;
    private static final float BYTE_SCALE = 1.0f / 255.0f;

    public Bitmap4x8FontRenderer(Resources resources,
            int forePaintColor, int backPaintColor) {
        super(forePaintColor, backPaintColor);
        mFont = BitmapFactory.decodeResource(resources, R.drawable.atari_small);
        mPaint = new Paint();
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    }

    public float getCharacterWidth() {
        return kCharacterWidth;
    }

    public int getCharacterHeight() {
        return kCharacterHeight;
    }

    public void drawTextRun(Canvas canvas, float x, float y,
            int lineOffset, char[] text, int index, int count,
            boolean cursor, int foreColor, int backColor) {
        setColorMatrix(mForePaint[foreColor & 7],
                cursor ? mCursorPaint : mBackPaint[backColor & 7]);
        int destX = (int) x + kCharacterWidth * lineOffset;
        int destY = (int) y;
        Rect srcRect = new Rect();
        Rect destRect = new Rect();
        destRect.top = (destY - kCharacterHeight);
        destRect.bottom = destY;
        for(int i = 0; i < count; i++) {
            char c = text[i + index];
            if ((cursor || (c != 32)) && (c < 128)) {
                int cellX = c & 31;
                int cellY = (c >> 5) & 3;
                int srcX = cellX * kCharacterWidth;
                int srcY = cellY * kCharacterHeight;
                srcRect.set(srcX, srcY,
                        srcX + kCharacterWidth, srcY + kCharacterHeight);
                destRect.left = destX;
                destRect.right = destX + kCharacterWidth;
                canvas.drawBitmap(mFont, srcRect, destRect, mPaint);
            }
            destX += kCharacterWidth;
        }
    }

    private void setColorMatrix(int foreColor, int backColor) {
        if ((foreColor != mCurrentForeColor)
                || (backColor != mCurrentBackColor)
                || (mColorMatrix == null)) {
            mCurrentForeColor = foreColor;
            mCurrentBackColor = backColor;
            if (mColorMatrix == null) {
                mColorMatrix = new float[20];
                mColorMatrix[18] = 1.0f; // Just copy Alpha
            }
            for (int component = 0; component < 3; component++) {
                int rightShift = (2 - component) << 3;
                int fore = 0xff & (foreColor >> rightShift);
                int back = 0xff & (backColor >> rightShift);
                int delta = back - fore;
                mColorMatrix[component * 6] = delta * BYTE_SCALE;
                mColorMatrix[component * 5 + 4] = fore;
            }
            mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
        }
    }
}

class PaintRenderer extends BaseTextRenderer {
    public PaintRenderer(int fontSize, int forePaintColor, int backPaintColor) {
        super(forePaintColor, backPaintColor);
        mTextPaint = new Paint();

	// our own TTF font
	String fontfile = Environment.getExternalStorageDirectory().toString()
	    + "/ZShaolin/" + "Font.ttf";
	File file = new File(fontfile);

	if(file.exists()) {
	    Typeface _font = Typeface.createFromFile(fontfile);
	    mTextPaint.setTypeface(_font);
	} else mTextPaint.setTypeface(Typeface.MONOSPACE);

        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize(fontSize);

        mCharHeight = (int) Math.ceil(mTextPaint.getFontSpacing());
        mCharAscent = (int) Math.ceil(mTextPaint.ascent());
        mCharDescent = mCharHeight + mCharAscent;
        mCharWidth = mTextPaint.measureText(EXAMPLE_CHAR, 0, 1);
    }

    public void drawTextRun(Canvas canvas, float x, float y, int lineOffset,
            char[] text, int index, int count,
            boolean cursor, int foreColor, int backColor) {
        if (cursor) {
            mTextPaint.setColor(mCursorPaint);
        } else {
            mTextPaint.setColor(mBackPaint[backColor & 0x7]);
        }
        float left = x + lineOffset * mCharWidth;
        canvas.drawRect(left, y + mCharAscent,
                left + count * mCharWidth, y + mCharDescent,
                mTextPaint);
        boolean bold = ( foreColor & 0x8 ) != 0;
        boolean underline = (backColor & 0x8) != 0;
        if (bold) {
            mTextPaint.setFakeBoldText(true);
        }
        if (underline) {
            mTextPaint.setUnderlineText(true);
        }
        mTextPaint.setColor(mForePaint[foreColor & 0x7]);
        canvas.drawText(text, index, count, left, y, mTextPaint);
        if (bold) {
            mTextPaint.setFakeBoldText(false);
        }
        if (underline) {
            mTextPaint.setUnderlineText(false);
        }
    }

    public int getCharacterHeight() {
        return mCharHeight;
    }

    public float getCharacterWidth() {
        return mCharWidth;
    }


    private Paint mTextPaint;
    private float mCharWidth;
    private int mCharHeight;
    private int mCharAscent;
    private int mCharDescent;
    private static final char[] EXAMPLE_CHAR = {'X'};
    }

/**
 * An ASCII key listener. Supports control characters and escape. Keeps track of
 * the current state of the alt, shift, and control keys.
 */
class TermKeyListener {
    /**
     * Android key codes that are defined in the Android 2.3 API.
     * We want to recognize these codes, because they will be sent to our
     * app when we run on Android 2.3 systems.
     * But we don't want to accidentally use 2.3-specific APIs.
     * So we compile against the Android 1.6 APIs, and have a copy of the codes here.
     */

    /** Key code constant: Unknown key code. */
    public static final int KEYCODE_UNKNOWN         = 0;
    /** Key code constant: Soft Left key.
     * Usually situated below the display on phones and used as a multi-function
     * feature key for selecting a software defined function shown on the bottom left
     * of the display. */
    public static final int KEYCODE_SOFT_LEFT       = 1;
    /** Key code constant: Soft Right key.
     * Usually situated below the display on phones and used as a multi-function
     * feature key for selecting a software defined function shown on the bottom right
     * of the display. */
    public static final int KEYCODE_SOFT_RIGHT      = 2;
    /** Key code constant: Home key.
     * This key is handled by the framework and is never delivered to applications. */
    public static final int KEYCODE_HOME            = 3;
    /** Key code constant: Back key. */
    public static final int KEYCODE_BACK            = 4;
    /** Key code constant: Call key. */
    public static final int KEYCODE_CALL            = 5;
    /** Key code constant: End Call key. */
    public static final int KEYCODE_ENDCALL         = 6;
    /** Key code constant: '0' key. */
    public static final int KEYCODE_0               = 7;
    /** Key code constant: '1' key. */
    public static final int KEYCODE_1               = 8;
    /** Key code constant: '2' key. */
    public static final int KEYCODE_2               = 9;
    /** Key code constant: '3' key. */
    public static final int KEYCODE_3               = 10;
    /** Key code constant: '4' key. */
    public static final int KEYCODE_4               = 11;
    /** Key code constant: '5' key. */
    public static final int KEYCODE_5               = 12;
    /** Key code constant: '6' key. */
    public static final int KEYCODE_6               = 13;
    /** Key code constant: '7' key. */
    public static final int KEYCODE_7               = 14;
    /** Key code constant: '8' key. */
    public static final int KEYCODE_8               = 15;
    /** Key code constant: '9' key. */
    public static final int KEYCODE_9               = 16;
    /** Key code constant: '*' key. */
    public static final int KEYCODE_STAR            = 17;
    /** Key code constant: '#' key. */
    public static final int KEYCODE_POUND           = 18;
    /** Key code constant: Directional Pad Up key.
     * May also be synthesized from trackball motions. */
    public static final int KEYCODE_DPAD_UP         = 19;
    /** Key code constant: Directional Pad Down key.
     * May also be synthesized from trackball motions. */
    public static final int KEYCODE_DPAD_DOWN       = 20;
    /** Key code constant: Directional Pad Left key.
     * May also be synthesized from trackball motions. */
    public static final int KEYCODE_DPAD_LEFT       = 21;
    /** Key code constant: Directional Pad Right key.
     * May also be synthesized from trackball motions. */
    public static final int KEYCODE_DPAD_RIGHT      = 22;
    /** Key code constant: Directional Pad Center key.
     * May also be synthesized from trackball motions. */
    public static final int KEYCODE_DPAD_CENTER     = 23;
    /** Key code constant: Volume Up key.
     * Adjusts the speaker volume up. */
    public static final int KEYCODE_VOLUME_UP       = 24;
    /** Key code constant: Volume Down key.
     * Adjusts the speaker volume down. */
    public static final int KEYCODE_VOLUME_DOWN     = 25;
    /** Key code constant: Power key. */
    public static final int KEYCODE_POWER           = 26;
    /** Key code constant: Camera key.
     * Used to launch a camera application or take pictures. */
    public static final int KEYCODE_CAMERA          = 27;
    /** Key code constant: Clear key. */
    public static final int KEYCODE_CLEAR           = 28;
    /** Key code constant: 'A' key. */
    public static final int KEYCODE_A               = 29;
    /** Key code constant: 'B' key. */
    public static final int KEYCODE_B               = 30;
    /** Key code constant: 'C' key. */
    public static final int KEYCODE_C               = 31;
    /** Key code constant: 'D' key. */
    public static final int KEYCODE_D               = 32;
    /** Key code constant: 'E' key. */
    public static final int KEYCODE_E               = 33;
    /** Key code constant: 'F' key. */
    public static final int KEYCODE_F               = 34;
    /** Key code constant: 'G' key. */
    public static final int KEYCODE_G               = 35;
    /** Key code constant: 'H' key. */
    public static final int KEYCODE_H               = 36;
    /** Key code constant: 'I' key. */
    public static final int KEYCODE_I               = 37;
    /** Key code constant: 'J' key. */
    public static final int KEYCODE_J               = 38;
    /** Key code constant: 'K' key. */
    public static final int KEYCODE_K               = 39;
    /** Key code constant: 'L' key. */
    public static final int KEYCODE_L               = 40;
    /** Key code constant: 'M' key. */
    public static final int KEYCODE_M               = 41;
    /** Key code constant: 'N' key. */
    public static final int KEYCODE_N               = 42;
    /** Key code constant: 'O' key. */
    public static final int KEYCODE_O               = 43;
    /** Key code constant: 'P' key. */
    public static final int KEYCODE_P               = 44;
    /** Key code constant: 'Q' key. */
    public static final int KEYCODE_Q               = 45;
    /** Key code constant: 'R' key. */
    public static final int KEYCODE_R               = 46;
    /** Key code constant: 'S' key. */
    public static final int KEYCODE_S               = 47;
    /** Key code constant: 'T' key. */
    public static final int KEYCODE_T               = 48;
    /** Key code constant: 'U' key. */
    public static final int KEYCODE_U               = 49;
    /** Key code constant: 'V' key. */
    public static final int KEYCODE_V               = 50;
    /** Key code constant: 'W' key. */
    public static final int KEYCODE_W               = 51;
    /** Key code constant: 'X' key. */
    public static final int KEYCODE_X               = 52;
    /** Key code constant: 'Y' key. */
    public static final int KEYCODE_Y               = 53;
    /** Key code constant: 'Z' key. */
    public static final int KEYCODE_Z               = 54;
    /** Key code constant: ',' key. */
    public static final int KEYCODE_COMMA           = 55;
    /** Key code constant: '.' key. */
    public static final int KEYCODE_PERIOD          = 56;
    /** Key code constant: Left Alt modifier key. */
    public static final int KEYCODE_ALT_LEFT        = 57;
    /** Key code constant: Right Alt modifier key. */
    public static final int KEYCODE_ALT_RIGHT       = 58;
    /** Key code constant: Left Shift modifier key. */
    public static final int KEYCODE_SHIFT_LEFT      = 59;
    /** Key code constant: Right Shift modifier key. */
    public static final int KEYCODE_SHIFT_RIGHT     = 60;
    /** Key code constant: Tab key. */
    public static final int KEYCODE_TAB             = 61;
    /** Key code constant: Space key. */
    public static final int KEYCODE_SPACE           = 62;
    /** Key code constant: Symbol modifier key.
     * Used to enter alternate symbols. */
    public static final int KEYCODE_SYM             = 63;
    /** Key code constant: Explorer special function key.
     * Used to launch a browser application. */
    public static final int KEYCODE_EXPLORER        = 64;
    /** Key code constant: Envelope special function key.
     * Used to launch a mail application. */
    public static final int KEYCODE_ENVELOPE        = 65;
    /** Key code constant: Enter key. */
    public static final int KEYCODE_ENTER           = 66;
    /** Key code constant: Backspace key.
     * Deletes characters before the insertion point, unlike {@link #KEYCODE_FORWARD_DEL}. */
    public static final int KEYCODE_DEL             = 67;
    /** Key code constant: '`' (backtick) key. */
    public static final int KEYCODE_GRAVE           = 68;
    /** Key code constant: '-'. */
    public static final int KEYCODE_MINUS           = 69;
    /** Key code constant: '=' key. */
    public static final int KEYCODE_EQUALS          = 70;
    /** Key code constant: '[' key. */
    public static final int KEYCODE_LEFT_BRACKET    = 71;
    /** Key code constant: ']' key. */
    public static final int KEYCODE_RIGHT_BRACKET   = 72;
    /** Key code constant: '\' key. */
    public static final int KEYCODE_BACKSLASH       = 73;
    /** Key code constant: ';' key. */
    public static final int KEYCODE_SEMICOLON       = 74;
    /** Key code constant: ''' (apostrophe) key. */
    public static final int KEYCODE_APOSTROPHE      = 75;
    /** Key code constant: '/' key. */
    public static final int KEYCODE_SLASH           = 76;
    /** Key code constant: '@' key. */
    public static final int KEYCODE_AT              = 77;
    /** Key code constant: Number modifier key.
     * Used to enter numeric symbols.
     * This key is not Num Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is
     * interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}. */
    public static final int KEYCODE_NUM             = 78;
    /** Key code constant: Headset Hook key.
     * Used to hang up calls and stop media. */
    public static final int KEYCODE_HEADSETHOOK     = 79;
    /** Key code constant: Camera Focus key.
     * Used to focus the camera. */
    public static final int KEYCODE_FOCUS           = 80;   // *Camera* focus
    /** Key code constant: '+' key. */
    public static final int KEYCODE_PLUS            = 81;
    /** Key code constant: Menu key. */
    public static final int KEYCODE_MENU            = 82;
    /** Key code constant: Notification key. */
    public static final int KEYCODE_NOTIFICATION    = 83;
    /** Key code constant: Search key. */
    public static final int KEYCODE_SEARCH          = 84;
    /** Key code constant: Play/Pause media key. */
    public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85;
    /** Key code constant: Stop media key. */
    public static final int KEYCODE_MEDIA_STOP      = 86;
    /** Key code constant: Play Next media key. */
    public static final int KEYCODE_MEDIA_NEXT      = 87;
    /** Key code constant: Play Previous media key. */
    public static final int KEYCODE_MEDIA_PREVIOUS  = 88;
    /** Key code constant: Rewind media key. */
    public static final int KEYCODE_MEDIA_REWIND    = 89;
    /** Key code constant: Fast Forward media key. */
    public static final int KEYCODE_MEDIA_FAST_FORWARD = 90;
    /** Key code constant: Mute key.
     * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */
    public static final int KEYCODE_MUTE            = 91;
    /** Key code constant: Page Up key. */
    public static final int KEYCODE_PAGE_UP         = 92;
    /** Key code constant: Page Down key. */
    public static final int KEYCODE_PAGE_DOWN       = 93;
    /** Key code constant: Picture Symbols modifier key.
     * Used to switch symbol sets (Emoji, Kao-moji). */
    public static final int KEYCODE_PICTSYMBOLS     = 94;   // switch symbol-sets (Emoji,Kao-moji)
    /** Key code constant: Switch Charset modifier key.
     * Used to switch character sets (Kanji, Katakana). */
    public static final int KEYCODE_SWITCH_CHARSET  = 95;   // switch char-sets (Kanji,Katakana)
    /** Key code constant: A Button key.
     * On a game controller, the A button should be either the button labeled A
     * or the first button on the upper row of controller buttons. */
    public static final int KEYCODE_BUTTON_A        = 96;
    /** Key code constant: B Button key.
     * On a game controller, the B button should be either the button labeled B
     * or the second button on the upper row of controller buttons. */
    public static final int KEYCODE_BUTTON_B        = 97;
    /** Key code constant: C Button key.
     * On a game controller, the C button should be either the button labeled C
     * or the third button on the upper row of controller buttons. */
    public static final int KEYCODE_BUTTON_C        = 98;
    /** Key code constant: X Button key.
     * On a game controller, the X button should be either the button labeled X
     * or the first button on the lower row of controller buttons. */
    public static final int KEYCODE_BUTTON_X        = 99;
    /** Key code constant: Y Button key.
     * On a game controller, the Y button should be either the button labeled Y
     * or the second button on the lower row of controller buttons. */
    public static final int KEYCODE_BUTTON_Y        = 100;
    /** Key code constant: Z Button key.
     * On a game controller, the Z button should be either the button labeled Z
     * or the third button on the lower row of controller buttons. */
    public static final int KEYCODE_BUTTON_Z        = 101;
    /** Key code constant: L1 Button key.
     * On a game controller, the L1 button should be either the button labeled L1 (or L)
     * or the top left trigger button. */
    public static final int KEYCODE_BUTTON_L1       = 102;
    /** Key code constant: R1 Button key.
     * On a game controller, the R1 button should be either the button labeled R1 (or R)
     * or the top right trigger button. */
    public static final int KEYCODE_BUTTON_R1       = 103;
    /** Key code constant: L2 Button key.
     * On a game controller, the L2 button should be either the button labeled L2
     * or the bottom left trigger button. */
    public static final int KEYCODE_BUTTON_L2       = 104;
    /** Key code constant: R2 Button key.
     * On a game controller, the R2 button should be either the button labeled R2
     * or the bottom right trigger button. */
    public static final int KEYCODE_BUTTON_R2       = 105;
    /** Key code constant: Left Thumb Button key.
     * On a game controller, the left thumb button indicates that the left (or only)
     * joystick is pressed. */
    public static final int KEYCODE_BUTTON_THUMBL   = 106;
    /** Key code constant: Right Thumb Button key.
     * On a game controller, the right thumb button indicates that the right
     * joystick is pressed. */
    public static final int KEYCODE_BUTTON_THUMBR   = 107;
    /** Key code constant: Start Button key.
     * On a game controller, the button labeled Start. */
    public static final int KEYCODE_BUTTON_START    = 108;
    /** Key code constant: Select Button key.
     * On a game controller, the button labeled Select. */
    public static final int KEYCODE_BUTTON_SELECT   = 109;
    /** Key code constant: Mode Button key.
     * On a game controller, the button labeled Mode. */
    public static final int KEYCODE_BUTTON_MODE     = 110;
    /** Key code constant: Escape key. */
    public static final int KEYCODE_ESCAPE          = 111;
    /** Key code constant: Forward Delete key.
     * Deletes characters ahead of the insertion point, unlike {@link #KEYCODE_DEL}. */
    public static final int KEYCODE_FORWARD_DEL     = 112;
    /** Key code constant: Left Control modifier key. */
    public static final int KEYCODE_CTRL_LEFT       = 113;
    /** Key code constant: Right Control modifier key. */
    public static final int KEYCODE_CTRL_RIGHT      = 114;
    /** Key code constant: Caps Lock modifier key. */
    public static final int KEYCODE_CAPS_LOCK       = 115;
    /** Key code constant: Scroll Lock key. */
    public static final int KEYCODE_SCROLL_LOCK     = 116;
    /** Key code constant: Left Meta modifier key. */
    public static final int KEYCODE_META_LEFT       = 117;
    /** Key code constant: Right Meta modifier key. */
    public static final int KEYCODE_META_RIGHT      = 118;
    /** Key code constant: Function modifier key. */
    public static final int KEYCODE_FUNCTION        = 119;
    /** Key code constant: System Request / Print Screen key. */
    public static final int KEYCODE_SYSRQ           = 120;
    /** Key code constant: Break / Pause key. */
    public static final int KEYCODE_BREAK           = 121;
    /** Key code constant: Home Movement key.
     * Used for scrolling or moving the cursor around to the start of a line
     * or to the top of a list. */
    public static final int KEYCODE_MOVE_HOME       = 122;
    /** Key code constant: End Movement key.
     * Used for scrolling or moving the cursor around to the end of a line
     * or to the bottom of a list. */
    public static final int KEYCODE_MOVE_END        = 123;
    /** Key code constant: Insert key.
     * Toggles insert / overwrite edit mode. */
    public static final int KEYCODE_INSERT          = 124;
    /** Key code constant: Forward key.
     * Navigates forward in the history stack.  Complement of {@link #KEYCODE_BACK}. */
    public static final int KEYCODE_FORWARD         = 125;
    /** Key code constant: Play media key. */
    public static final int KEYCODE_MEDIA_PLAY      = 126;
    /** Key code constant: Pause media key. */
    public static final int KEYCODE_MEDIA_PAUSE     = 127;
    /** Key code constant: Close media key.
     * May be used to close a CD tray, for example. */
    public static final int KEYCODE_MEDIA_CLOSE     = 128;
    /** Key code constant: Eject media key.
     * May be used to eject a CD tray, for example. */
    public static final int KEYCODE_MEDIA_EJECT     = 129;
    /** Key code constant: Record media key. */
    public static final int KEYCODE_MEDIA_RECORD    = 130;
    /** Key code constant: F1 key. */
    public static final int KEYCODE_F1              = 131;
    /** Key code constant: F2 key. */
    public static final int KEYCODE_F2              = 132;
    /** Key code constant: F3 key. */
    public static final int KEYCODE_F3              = 133;
    /** Key code constant: F4 key. */
    public static final int KEYCODE_F4              = 134;
    /** Key code constant: F5 key. */
    public static final int KEYCODE_F5              = 135;
    /** Key code constant: F6 key. */
    public static final int KEYCODE_F6              = 136;
    /** Key code constant: F7 key. */
    public static final int KEYCODE_F7              = 137;
    /** Key code constant: F8 key. */
    public static final int KEYCODE_F8              = 138;
    /** Key code constant: F9 key. */
    public static final int KEYCODE_F9              = 139;
    /** Key code constant: F10 key. */
    public static final int KEYCODE_F10             = 140;
    /** Key code constant: F11 key. */
    public static final int KEYCODE_F11             = 141;
    /** Key code constant: F12 key. */
    public static final int KEYCODE_F12             = 142;
    /** Key code constant: Num Lock modifier key.
     * This is the Num Lock key; it is different from {@link #KEYCODE_NUM}.
     * This key generally modifies the behavior of other keys on the numeric keypad. */
    public static final int KEYCODE_NUM_LOCK        = 143;
    /** Key code constant: Numeric keypad '0' key. */
    public static final int KEYCODE_NUMPAD_0        = 144;
    /** Key code constant: Numeric keypad '1' key. */
    public static final int KEYCODE_NUMPAD_1        = 145;
    /** Key code constant: Numeric keypad '2' key. */
    public static final int KEYCODE_NUMPAD_2        = 146;
    /** Key code constant: Numeric keypad '3' key. */
    public static final int KEYCODE_NUMPAD_3        = 147;
    /** Key code constant: Numeric keypad '4' key. */
    public static final int KEYCODE_NUMPAD_4        = 148;
    /** Key code constant: Numeric keypad '5' key. */
    public static final int KEYCODE_NUMPAD_5        = 149;
    /** Key code constant: Numeric keypad '6' key. */
    public static final int KEYCODE_NUMPAD_6        = 150;
    /** Key code constant: Numeric keypad '7' key. */
    public static final int KEYCODE_NUMPAD_7        = 151;
    /** Key code constant: Numeric keypad '8' key. */
    public static final int KEYCODE_NUMPAD_8        = 152;
    /** Key code constant: Numeric keypad '9' key. */
    public static final int KEYCODE_NUMPAD_9        = 153;
    /** Key code constant: Numeric keypad '/' key (for division). */
    public static final int KEYCODE_NUMPAD_DIVIDE   = 154;
    /** Key code constant: Numeric keypad '*' key (for multiplication). */
    public static final int KEYCODE_NUMPAD_MULTIPLY = 155;
    /** Key code constant: Numeric keypad '-' key (for subtraction). */
    public static final int KEYCODE_NUMPAD_SUBTRACT = 156;
    /** Key code constant: Numeric keypad '+' key (for addition). */
    public static final int KEYCODE_NUMPAD_ADD      = 157;
    /** Key code constant: Numeric keypad '.' key (for decimals or digit grouping). */
    public static final int KEYCODE_NUMPAD_DOT      = 158;
    /** Key code constant: Numeric keypad ',' key (for decimals or digit grouping). */
    public static final int KEYCODE_NUMPAD_COMMA    = 159;
    /** Key code constant: Numeric keypad Enter key. */
    public static final int KEYCODE_NUMPAD_ENTER    = 160;
    /** Key code constant: Numeric keypad '=' key. */
    public static final int KEYCODE_NUMPAD_EQUALS   = 161;
    /** Key code constant: Numeric keypad '(' key. */
    public static final int KEYCODE_NUMPAD_LEFT_PAREN = 162;
    /** Key code constant: Numeric keypad ')' key. */
    public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163;
    /** Key code constant: Volume Mute key.
     * Mutes the speaker, unlike {@link #KEYCODE_MUTE}.
     * This key should normally be implemented as a toggle such that the first press
     * mutes the speaker and the second press restores the original volume. */
    public static final int KEYCODE_VOLUME_MUTE     = 164;
    /** Key code constant: Info key.
     * Common on TV remotes to show additional information related to what is
     * currently being viewed. */
    public static final int KEYCODE_INFO            = 165;
    /** Key code constant: Channel up key.
     * On TV remotes, increments the television channel. */
    public static final int KEYCODE_CHANNEL_UP      = 166;
    /** Key code constant: Channel down key.
     * On TV remotes, decrements the television channel. */
    public static final int KEYCODE_CHANNEL_DOWN    = 167;
    /** Key code constant: Zoom in key. */
    public static final int KEYCODE_ZOOM_IN         = 168;
    /** Key code constant: Zoom out key. */
    public static final int KEYCODE_ZOOM_OUT        = 169;
    /** Key code constant: TV key.
     * On TV remotes, switches to viewing live TV. */
    public static final int KEYCODE_TV              = 170;
    /** Key code constant: Window key.
     * On TV remotes, toggles picture-in-picture mode or other windowing functions. */
    public static final int KEYCODE_WINDOW          = 171;
    /** Key code constant: Guide key.
     * On TV remotes, shows a programming guide. */
    public static final int KEYCODE_GUIDE           = 172;
    /** Key code constant: DVR key.
     * On some TV remotes, switches to a DVR mode for recorded shows. */
    public static final int KEYCODE_DVR             = 173;
    /** Key code constant: Bookmark key.
     * On some TV remotes, bookmarks content or web pages. */
    public static final int KEYCODE_BOOKMARK        = 174;
    /** Key code constant: Toggle captions key.
     * Switches the mode for closed-captioning text, for example during television shows. */
    public static final int KEYCODE_CAPTIONS        = 175;
    /** Key code constant: Settings key.
     * Starts the system settings activity. */
    public static final int KEYCODE_SETTINGS        = 176;
    /** Key code constant: TV power key.
     * On TV remotes, toggles the power on a television screen. */
    public static final int KEYCODE_TV_POWER        = 177;
    /** Key code constant: TV input key.
     * On TV remotes, switches the input on a television screen. */
    public static final int KEYCODE_TV_INPUT        = 178;
    /** Key code constant: Set-top-box power key.
     * On TV remotes, toggles the power on an external Set-top-box. */
    public static final int KEYCODE_STB_POWER       = 179;
    /** Key code constant: Set-top-box input key.
     * On TV remotes, switches the input mode on an external Set-top-box. */
    public static final int KEYCODE_STB_INPUT       = 180;
    /** Key code constant: A/V Receiver power key.
     * On TV remotes, toggles the power on an external A/V Receiver. */
    public static final int KEYCODE_AVR_POWER       = 181;
    /** Key code constant: A/V Receiver input key.
     * On TV remotes, switches the input mode on an external A/V Receiver. */
    public static final int KEYCODE_AVR_INPUT       = 182;
    /** Key code constant: Red "programmable" key.
     * On TV remotes, acts as a contextual/programmable key. */
    public static final int KEYCODE_PROG_RED        = 183;
    /** Key code constant: Green "programmable" key.
     * On TV remotes, actsas a contextual/programmable key. */
    public static final int KEYCODE_PROG_GREEN      = 184;
    /** Key code constant: Yellow "programmable" key.
     * On TV remotes, acts as a contextual/programmable key. */
    public static final int KEYCODE_PROG_YELLOW     = 185;
    /** Key code constant: Blue "programmable" key.
     * On TV remotes, acts as a contextual/programmable key. */
    public static final int KEYCODE_PROG_BLUE       = 186;

    private static final int LAST_KEYCODE           = KEYCODE_PROG_BLUE;

    private String[] mKeyCodes = new String[256];
    private String[] mCTRL_KeyCodes = new String[4];
    private String[] mAppKeyCodes = new String[256];

    private void initKeyCodes() {
        mKeyCodes[KEYCODE_DPAD_CENTER] = "\015";

        mKeyCodes[KEYCODE_DPAD_UP] = "\033[A";
        mKeyCodes[KEYCODE_DPAD_DOWN] = "\033[B";
        mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033[C";
        mKeyCodes[KEYCODE_DPAD_LEFT] = "\033[D";

        mCTRL_KeyCodes[0] = "\033[1;5A";
        mCTRL_KeyCodes[1] = "\033[1;5B";
        mCTRL_KeyCodes[2] = "\033[1;5C";
        mCTRL_KeyCodes[3] = "\033[1;5D";

        mKeyCodes[KEYCODE_F1] = "\033OP";
        mKeyCodes[KEYCODE_F2] = "\033OQ";
        mKeyCodes[KEYCODE_F3] = "\033OR";
        mKeyCodes[KEYCODE_F4] = "\033OS";

//        mKeyCodes[KEYCODE_F1] = "\033[11~";
//        mKeyCodes[KEYCODE_F2] = "\033[12~";
//        mKeyCodes[KEYCODE_F3] = "\033[13~";
//        mKeyCodes[KEYCODE_F4] = "\033[14~";

        mKeyCodes[KEYCODE_F5] = "\033[15~";
        mKeyCodes[KEYCODE_F6] = "\033[17~";
        mKeyCodes[KEYCODE_F7] = "\033[18~";
        mKeyCodes[KEYCODE_F8] = "\033[19~";
        mKeyCodes[KEYCODE_F9] = "\033[20~";
        mKeyCodes[KEYCODE_F10] = "\033[21~";
        mKeyCodes[KEYCODE_F11] = "\033[23~";
        mKeyCodes[KEYCODE_F12] = "\033[24~";
        mKeyCodes[KEYCODE_SYSRQ] = "\033[32~"; // Sys Request / Print
        // Is this Scroll lock? mKeyCodes[Cancel] = "\033[33~";
        mKeyCodes[KEYCODE_BREAK] = "\033[34~"; // Pause/Break

        mKeyCodes[KEYCODE_TAB] = "\011";
        mKeyCodes[KEYCODE_ENTER] = "\015";
        mKeyCodes[KEYCODE_ESCAPE] = "\033";

        mKeyCodes[KEYCODE_INSERT] = "\033[2~";
        mKeyCodes[KEYCODE_FORWARD_DEL] = "\033[3~";
        mKeyCodes[KEYCODE_MOVE_HOME] = "\033[1~";
        mKeyCodes[KEYCODE_MOVE_END] = "\033[4~";
        mKeyCodes[KEYCODE_PAGE_UP] = "\033[5~";
        mKeyCodes[KEYCODE_PAGE_DOWN] = "\033[6~";
        mKeyCodes[KEYCODE_DEL]= "\177";
        mKeyCodes[KEYCODE_NUM_LOCK] = "\033OP";
        mKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "/";
        mKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "*";
        mKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "-";
        mKeyCodes[KEYCODE_NUMPAD_ADD] = "+";
        mKeyCodes[KEYCODE_NUMPAD_ENTER] = "\015";
        mKeyCodes[KEYCODE_NUMPAD_EQUALS] = "=";
        mKeyCodes[KEYCODE_NUMPAD_DOT] = ".";
        mKeyCodes[KEYCODE_NUMPAD_COMMA] = ",";
        mKeyCodes[KEYCODE_NUMPAD_0] = "0";
        mKeyCodes[KEYCODE_NUMPAD_1] = "1";
        mKeyCodes[KEYCODE_NUMPAD_2] = "2";
        mKeyCodes[KEYCODE_NUMPAD_3] = "3";
        mKeyCodes[KEYCODE_NUMPAD_4] = "4";
        mKeyCodes[KEYCODE_NUMPAD_5] = "5";
        mKeyCodes[KEYCODE_NUMPAD_6] = "6";
        mKeyCodes[KEYCODE_NUMPAD_7] = "7";
        mKeyCodes[KEYCODE_NUMPAD_8] = "8";
        mKeyCodes[KEYCODE_NUMPAD_9] = "9";

        mAppKeyCodes[KEYCODE_DPAD_UP] = "\033OA";
        mAppKeyCodes[KEYCODE_DPAD_DOWN] = "\033OB";
        mAppKeyCodes[KEYCODE_DPAD_RIGHT] = "\033OC";
        mAppKeyCodes[KEYCODE_DPAD_LEFT] = "\033OD";
        mAppKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "\033Oo";
        mAppKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "\033Oj";
        mAppKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "\033Om";
        mAppKeyCodes[KEYCODE_NUMPAD_ADD] = "\033Ok";
        mAppKeyCodes[KEYCODE_NUMPAD_ENTER] = "\033OM";
        mAppKeyCodes[KEYCODE_NUMPAD_EQUALS] = "\033OX";
        mAppKeyCodes[KEYCODE_NUMPAD_DOT] = "\033On";
        mAppKeyCodes[KEYCODE_NUMPAD_COMMA] = "\033Ol";
        mAppKeyCodes[KEYCODE_NUMPAD_0] = "\033Op";
        mAppKeyCodes[KEYCODE_NUMPAD_1] = "\033Oq";
        mAppKeyCodes[KEYCODE_NUMPAD_2] = "\033Or";
        mAppKeyCodes[KEYCODE_NUMPAD_3] = "\033Os";
        mAppKeyCodes[KEYCODE_NUMPAD_4] = "\033Ot";
        mAppKeyCodes[KEYCODE_NUMPAD_5] = "\033Ou";
        mAppKeyCodes[KEYCODE_NUMPAD_6] = "\033Ov";
        mAppKeyCodes[KEYCODE_NUMPAD_7] = "\033Ow";
        mAppKeyCodes[KEYCODE_NUMPAD_8] = "\033Ox";
        mAppKeyCodes[KEYCODE_NUMPAD_9] = "\033Oy";
    }

    /**
     * The state engine for a modifier key. Can be pressed, released, locked,
     * and so on.
     *
     */
    private class ModifierKey {

        private int mState;

        private static final int UNPRESSED = 0;

        private static final int PRESSED = 1;

        private static final int RELEASED = 2;

        private static final int USED = 3;

        private static final int LOCKED = 4;

        private static final int ACTIVE = 5;
        private static final int OFF    = 6;

        /**
         * Construct a modifier key. UNPRESSED by default.
         *
         */
        public ModifierKey() {
//            mState = UNPRESSED;
            mState = OFF;
        }

        public void onPress() {
            /*switch (mState) {
            case PRESSED:
                // This is a repeat before use
                break;
            case RELEASED:
                mState = LOCKED;
                break;
            case USED:
                // This is a repeat after use
                break;
            case LOCKED:
                mState = UNPRESSED;
                break;
            default:
                mState = PRESSED;
                break;
            }*/
            mState = ACTIVE;
        }

        public void onRelease() {
            /*switch (mState) {
            case USED:
                mState = UNPRESSED;
                break;
            case PRESSED:
                mState = RELEASED;
                break;
            default:
                // Leave state alone
                break;
            }*/
            mState = OFF;
        }

        public void adjustAfterKeypress() {
            mState = OFF;
            
            /*switch (mState) {
            case PRESSED:
                mState = USED;
                break;
            case RELEASED:
                mState = UNPRESSED;
                break;
            default:
                // Leave state alone
                break;
            }*/
        }

        public boolean isActive() {
//            return mState != UNPRESSED;
            return mState == ACTIVE;
        }
    }

    private ModifierKey mAltKey = new ModifierKey();

    private ModifierKey mCapKey = new ModifierKey();

    private ModifierKey mControlKey = new ModifierKey();

    private ModifierKey mFnKey = new ModifierKey();

    private ModifierKey mFunctionKey = new ModifierKey();

    private boolean mCapsLock;

    static public final int KEYCODE_OFFSET = 1000;

    /**
     * Construct a term key listener.
     *
     */
    public TermKeyListener() {
        initKeyCodes();
    }

    public void handleControlKey(boolean down) {
//        Log.v("SpartacusRex","handleControlKey "+down);

        if (down) {
            mControlKey.onPress();
        } else {
            mControlKey.onRelease();
        }
    }

    public void handleFnKey(boolean down) {
        if (down) {
            //Send the ESC sequence
            mFnKey.onPress();
        } else {
            mFnKey.onRelease();
        }
    }

    public void handleFunctionKey(boolean down) {
        if (down) {
            //Send the ESC sequence
            mFunctionKey.onPress();
        } else {
            mFunctionKey.onRelease();
        }
    }

    public int mapControlChar(int ch) {
        int result = ch;
        if (mControlKey.isActive()) {
//            Log.v("SpartacusRex","mapControlChar CONTROL ON ");
            
            // Search is the control key.
            if (result >= 'a' && result <= 'z') {
                result = (char) (result - 'a' + '\001');
            } else if (result >= 'A' && result <= 'Z') {
                result = (char) (result - 'A' + '\001');
            } else if (result == ' ' || result == '2') {
                result = 0;
            } else if (result == '[' || result == '3') {
                result = 27; // ^[ (Esc)
            } else if (result == '\\' || result == '4') {
                result = 28;
            } else if (result == ']' || result == '5') {
                result = 29;
            } else if (result == '^' || result == '6') {
                result = 30; // control-^
            } else if (result == '_' || result == '7') {
                result = 31;
            }
            else if (result == '8') {
                result = 127; // DEL
            }
//            else if (result == '9') {
//                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F11;
//            } else if (result == '0') {
//                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F12;
//            }

            //Check if this is a one off
            if(mCTRLfromMETA){
//                Log.v("SpartacusRex","mapControlChar RELEASE CTRL from META");
               mCTRLfromMETA = false;
               mControlKey.onRelease();
            }

        } else if (mFunctionKey.isActive()) {

            if (result > '0' && result <= '9') {
                int offs = (int)'1';
                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F1 + result - offs;
            } else if (result == '0') {
                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F10;
            }else if (result == 'o' || result == 'O') {
                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F11;
            }else if (result == 'p'|| result == 'P') {
                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F12;
            }

        } else if (mFnKey.isActive()) {
//            Log.v("SpartacusRex","mapControlChar mFnKey.isActive() ");

/*            if (result == 'w' || result == 'W') {
                result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP;
            } else if (result == 'a' || result == 'A') {
                result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_LEFT;
            } else if (result == 's' || result == 'S') {
                result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_DOWN;
            } else if (result == 'd' || result == 'D') {
                result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_RIGHT;
            } else if (result == 'p' || result == 'P') {
                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_UP;
            } else if (result == 'n' || result == 'N') {
                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_DOWN;
            } else if (result == 't' || result == 'T') {
                result = KEYCODE_OFFSET + KeyEvent.KEYCODE_TAB;
            } else if (result == 'l' || result == 'L') {
                result = '|';
            } else if (result == 'u' || result == 'U') {
                result = '_';
            } else if (result == 'e' || result == 'E') {
                result = 27; // ^[ (Esc)
            } else if (result == '.') {
                result = 28; // ^\
            } else if (result > '0' && result <= '9') {
                // F1-F9
                result = (char)(result + KEYCODE_OFFSET + TermKeyListener.KEYCODE_F1 - 1);
            } else if (result == '0') {
                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F10;
            } else if (result == 'i' || result == 'I') {
                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_INSERT;
            } else if (result == 'x' || result == 'X') {
                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_FORWARD_DEL;
            } else if (result == 'h' || result == 'H') {
                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_HOME;
            } else if (result == 'f' || result == 'F') {
                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_END;
            }
 */
        }

        /*if (result > -1) {
            mAltKey.adjustAfterKeypress();
            mCapKey.adjustAfterKeypress();
            mControlKey.adjustAfterKeypress();
            mFnKey.adjustAfterKeypress();
        }*/

//        Log.v("SpartacusRex","mapControlChar "+ch+" --> "+result);

        return result;
    }

    /**
     * Handle a keyDown event.
     *
     * @param keyCode the keycode of the keyDown event
     *
     */
    public boolean mCTRLfromMETA = false;
    public void keyDown(int keyCode, KeyEvent event, OutputStream out, boolean appMode) throws IOException {
//        Log.v("SpartacusRex","EMVIEW : keyDown "+keyCode+ " meta:"+event.getMetaState()+" unicode:"+event.getUnicodeChar());

        if (handleKeyCode(keyCode, out, appMode)) {
//            Log.v("SpartacusRex","EMVIEW : Easy handle..");
            return;
        }

        boolean CTRL = (event.getMetaState() & 8) != 0;
        if(CTRL){
//            Log.v("SpartacusRex","EMVIEW : CONTROL PRESSED AND META SET!");
            mControlKey.onPress();
            mCTRLfromMETA = true;
        }else if(mCTRLfromMETA){
            mCTRLfromMETA = false;
//            Log.v("SpartacusRex","EMVIEW : NO CONTROL PRESSED AND META NOT SET!");
            mControlKey.onRelease();
        }

        int result = -1;
        switch (keyCode) {
        case -100:
            //it's a forward delete..
//            Log.v("SpartacusRex","EMVIEW : FORW DELETE");
            result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_FORWARD_DEL;
            break;

            case -98:
        case KeyEvent.KEYCODE_ALT_RIGHT:
        case KeyEvent.KEYCODE_ALT_LEFT:
//            Log.v("SpartacusRex","EMVIEW : ALT ON");
            mAltKey.onPress();
            break;

        case KeyEvent.KEYCODE_SHIFT_LEFT:
        case KeyEvent.KEYCODE_SHIFT_RIGHT:
            mCapKey.onPress();
            break;

        case -99:
        case KEYCODE_CTRL_LEFT:
        case KEYCODE_CTRL_RIGHT:
//            Log.v("SpartacusRex","EMVIEW : CTRL ON");
            mControlKey.onPress();
            break;

        case KEYCODE_CAPS_LOCK:
            if (event.getRepeatCount() == 0) {
                mCapsLock = !mCapsLock;
            }
            break;

        default: {

//            result = event.getUnicodeChar(
//                   (mCapKey.isActive() || mCapsLock ? KeyEvent.META_SHIFT_ON : 0) |
//                   (mAltKey.isActive() ? KeyEvent.META_ALT_ON : 0));
            result = event.getUnicodeChar( (mCapKey.isActive() || mCapsLock ? KeyEvent.META_SHIFT_ON : 0) );

            break;
            }
        }

        //Check for CTRL / FN key manipulation
        result = mapControlChar(result);

        if (result >= KEYCODE_OFFSET) {
            handleKeyCode(result - KEYCODE_OFFSET, out, appMode);
            
        } else if (result >= 0) {
            //Check
            if(mAltKey.isActive()){
                //Send Escape First..
                out.write(27);
            }

//            Log.v("SpartacusRex","TERM : write code:"+keyCode+" result:"+result);
            out.write(result);
        }
    }

    public boolean handleKeyCode(int keyCode, OutputStream out, boolean appMode) throws IOException {
//        Log.v("SpartacusRex","handleKeyCode "+keyCode);

        //Special handling -19 >> -22
        String code = null;
        if(keyCode>=-22 && keyCode<=-19){
            if(keyCode==-19){
               code = mCTRL_KeyCodes[0];
            }else if(keyCode==-20){
               code = mCTRL_KeyCodes[1];
            }else if(keyCode==-21){
               code = mCTRL_KeyCodes[3];
            }else if(keyCode==-22){
               code = mCTRL_KeyCodes[2];
            }

            int length = code.length();
            for (int i = 0; i < length; i++) {
                char cc = code.charAt(i);
                out.write(cc);
            }

            return true;
            
        }else if (keyCode >= 0 && keyCode < mKeyCodes.length) {
            if (appMode) {
                code = mAppKeyCodes[keyCode];
            }
            if (code == null) {
                code = mKeyCodes[keyCode];
            }
            if (code != null) {
                int length = code.length();
                for (int i = 0; i < length; i++) {
                    char cc = code.charAt(i);
//                    Log.v("SpartacusRex","TERM : handleKeyCode internal "+keyCode+" "+i+") "+(int)cc+" : "+cc);
                    out.write(cc);
                }
                return true;
            }
        }
        return false;
    }

    /**
     * Handle a keyUp event.
     *
     * @param keyCode the keyCode of the keyUp event
     */
    public void keyUp(int keyCode) {
	//        Log.v("ZShaolin","EMVIEW : keyup "+keyCode);
        
        switch (keyCode) {

        case -98:
        case KeyEvent.KEYCODE_ALT_LEFT:
        case KeyEvent.KEYCODE_ALT_RIGHT:
	    //            Log.v("ZShaolin","EMVIEW : ALT OFF");
            mAltKey.onRelease();
            break;
        case KeyEvent.KEYCODE_SHIFT_LEFT:
        case KeyEvent.KEYCODE_SHIFT_RIGHT:
            mCapKey.onRelease();
            break;

        case -99:
        case KEYCODE_CTRL_LEFT:
        case KEYCODE_CTRL_RIGHT:
//            Log.v("ZShaolin","EMVIEW : CTRL OFF");
            mControlKey.onRelease();
            break;

        default:
            // Ignore other keyUps
            break;
        }
    }
}
